package testApplets;

import java.util.Vector;

import marchingCubes.DualCube;
import marchingCubes.DualCubeEngine;
import marchingCubes.DualEdge;
import marchingCubes.IsMCVertex;
import marchingCubes.MCube;
import peasy.PeasyCam;
import processing.core.PApplet;
import simpleGeom.BasicMesh;
import simpleGeom.Bounds3D;
import simpleGeom.Box3D;
import simpleGeom.SimpleTriangle;
import simpleGeom.TrianglePreCalc;
import simpleGeom.Vec3D;
import testApplets.MeshToSolidAll.Cell;



public class DualTestApplet extends PApplet {
	Box3D b3d;
	BasicMesh mesh = new BasicMesh();
	DualCubeEngine dc;
Vector<SimpleTriangle>dcFaces;
	public void setup() {
	
		size(800,600,P3D);
		mesh = new BasicMesh();
		mesh.load("/Users/administrator/Desktop/testScene3.obj");
		
		Bounds3D cB = mesh.getBounds3D();
		b3d=cB.getOffsetBox(cB.getMaxDimension()*0.1f);
		println(b3d);
		
		dc = new DualCubeEngine();
		dc.init(b3d,b3d.getMaxDimension()/130);
		for (int i=0;i<mesh.faces.size();i++){
			SimpleTriangle face=(SimpleTriangle) mesh.faces.get(i);
			dc.scanTriangle(face.getNode(0), face.getNode(1), face.getNode(2));
		}
		for (int x=0;x<dc.nX;x++){
			for (int y=0;y<dc.nY;y++){
				for (int z=0;z<dc.nZ;z++){
					DualCube cube=dc.getCube(x, y, z);
					if (mesh.contains(dc.getXFloat(cube.x), dc.getYFloat(cube.y),dc.getZFloat(cube.z))){
						cube.setInside(true);
						cube.setValue(0);
					}
					else{
						cube.setValue(1);
					}
				}
			}
		}
		dcFaces=dc.getFaces();
		//dualCube();
		//dualCube();
		//println("faces: "+dcFaces.size());
		PeasyCam cam=new PeasyCam(this,b3d.getMaxDimension()*200);
		
	}
	

	public void displayEdge(DualEdge edge){
		//edge.display(g);
		DualCube c1=edge.c1;
		DualCube c2=edge.c2;
		Vec3D v1=new Vec3D(dc.getXFloat(c1.x),dc.getYFloat(c1.y),dc.getZFloat(c1.z));
		Vec3D v2=new Vec3D(dc.getXFloat(c2.x),dc.getYFloat(c2.y),dc.getZFloat(c2.z));
		line(v1.x,v1.y,v1.z,v2.x,v2.y,v2.z);
		fill(255,0,0);
		if (c1.isInside()){
			pushMatrix();
			translate(v1.x,v1.y,v1.z);
			box(0.01f);
			popMatrix();
		}
		if (c2.isInside()){
			pushMatrix();
			translate(v2.x,v2.y,v2.z);
			box(0.01f);
			popMatrix();
		}
		fill(0,255,0);
		Vec3D v=edge.intersection;
		if (v!=null){
			pushMatrix();
			translate(v.x,v.y,v.z);
			box(0.07f);
			popMatrix();
		}
	}
	
	public void draw() {
		background(255);
		scale(100);
		lights();
		pointLight(200,200,200,2,2,2);
		pointLight(200,200,200,-2,-2,-2);
		//mesh.display3D(g);
		fill(0,255,0);
		/*for (int x=0;x<dc.nX-1;x++){
			for (int y=0;y<dc.nY-1;y++){
				for (int z=0;z<dc.nZ-1;z++){
					DualEdge edge=dc.getEdge(x, y, z, x+1, y, z);
					if (edge!=null)displayEdge(edge);
					edge=dc.getEdge(x, y, z, x, y+1, z);
					if (edge!=null)displayEdge(edge);
					edge=dc.getEdge(x, y, z, x, y, z+1);
					if (edge!=null)displayEdge(edge);
				}
			}
		}*/
		
		/*for (int x=0;x<dc.nX;x++){
			for (int y=0;y<dc.nY;y++){
				for (int z=0;z<dc.nZ;z++){
					DualCube cube=dc.getCube(x, y, z);
					if (cube.isInside()){
						pushMatrix();
						translate(dc.getXFloat(cube.x), dc.getYFloat(cube.y),dc.getZFloat(cube.z));
						box(0.05f);
						popMatrix();
					}
				}
			}
		}*/
		fill(255);
		
		for (int i=0;i<dcFaces.size();i++){
			SimpleTriangle tri=dcFaces.get(i);
			tri.display3D(g);
		}
		pushMatrix();
		translate(sX,sY,sZ);
		scale(dim);
		for (int x=0;x<nX;x++){
			for (int y=0;y<nY;y++){
				for (int z=0;z<nZ;z++){
					Cell c=grid[x][y][z];
					if (!c.reached){
						pushMatrix();
						translate(c.x,c.y,c.z);
						//box(1);
						popMatrix();
					}
				}
			}
				
		}
		popMatrix();
		/*pushMatrix();
		translate(sX,sY,sZ);
		scale(dim);
		for (int i=0;i<dualMesh.faces.size();i++){
			SimpleTriangle tri=(SimpleTriangle) dualMesh.faces.get(i);
			tri.display3D(g);
		}
		
		popMatrix();*/
				
	}
	int nX,nY,nZ;
	float sX,sY,sZ;
	float dim=0.1f;
	float iso=0.15f;
	Cell[][][] grid;
	public int xK(float x) {
		return (int) ((x - sX) / dim);
	}

	public int yK(float y) {
		return (int) ((y - sY) / dim);
	}

	public int zK(float z) {
		return (int) ((z - sZ) / dim);
	}
	BasicMesh dualMesh;
	public void dualCube() {
		// findOutline(0, (int) (nY * 0.5f), (int) (nZ * 0.5f));
		createGrid();
		calculateDistances();
		findOutline(0,0,0);
		DualCube[][][] values = new DualCube[nX][nY][nZ];
		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					DualCube cube=new DualCube();
					cube.x=(short)x;
					cube.y=(short)y;
					cube.z=(short)z;
					 cube.setValue(grid[x][y][z].getValue());
					 cube.normal=grid[x][y][z].normal;
					 values[x][y][z]=cube;
				}
			}
		}
		println("values");
		DualCubeEngine dc = new DualCubeEngine();
		//dcFaces=;
	
		dualMesh = new BasicMesh(dc.getFaces(values, iso));
		dualMesh.load(dc.getFaces(values, iso));
		//dualMesh.scale(dim);
	
		System.out.println("faces: "+dualMesh.faces.size());
		// this.saveObjPrintwriter(pathObjOut);
	}
	public void createGrid() {
		nX = (int) (b3d.getDimX() / dim) + 1;
		nY = (int) (b3d.getDimY() / dim) + 1;
		nZ = (int) (b3d.getDimZ() / dim) + 1;

		sX = b3d.getX1();
		sY = b3d.getY1();
		sZ = b3d.getZ1();
		grid = new Cell[nX][nY][nZ];
		println(nX + " " + nY + " " + nZ);

		for (int x = 0; x < nX; x++) {
			println("x: " + x + "/" + nX);
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					float x1 = x * dim + sX;
					float y1 = y * dim + sY;
					float z1 = z * dim + sZ;
					Cell cell = new Cell(x, y, z);

					cell.setValue(1000);
					grid[x][y][z] = cell;
				}
			}
		}
	}

	public void calculateDistances() {

		// Box3D box3D = b3d.getOffsetBox(dim*4f+iso);
		TrianglePreCalc tri;
		for (int i = 0; i < mesh.faces.size(); i++) {
			if (i % 100000 == 0) {
				println(i + "/" + mesh.faces.size());
			}
			SimpleTriangle f = (SimpleTriangle) mesh.faces.get(i);
			 tri=new TrianglePreCalc(f.getNode(0),f.getNode(1),f.getNode(2));
			Box3D faceBounds = tri.getBounds3D();
			faceBounds = faceBounds.getOffsetBox(iso*2f);
			//faceBounds = faceBounds.getOffsetBox(dim*2.5f);
			int x1 = xK(faceBounds.x1) - 1;
			int y1 = yK(faceBounds.y1) - 1;
			int z1 = zK(faceBounds.z1) - 1;
			x1 = max(x1, 0);
			y1 = max(y1, 0);
			z1 = max(z1, 0);
			int x2 = xK(faceBounds.x2) + 2;
			int y2 = yK(faceBounds.y2) + 2;
			int z2 = zK(faceBounds.z2) + 2;
			x2 = min(x2, nX);
			y2 = min(y2, nY);
			z2 = min(z2, nZ);

			for (int x = x1; x < x2; x++) {
				for (int y = y1; y < y2; y++) {
					for (int z = z1; z < z2; z++) {
						// float minD = Float.MAX_VALUE;
						Cell cell = grid[x][y][z];
						float cellX = cell.getX();
						float cellY = cell.getY();
						float cellZ = cell.getZ();
						if (faceBounds.isOverlap(cellX - dim, cellY - dim,
								cellZ - dim, cellX + dim, cellY + dim, cellZ
										+ dim)) {
							float planeDist =tri.getDistancePlane(cellX, cellY, cellZ);
							if (planeDist < cell.getValue()) {
								float cDist = Float.MAX_VALUE;
								Integer res=new Integer(0);
								cDist=tri.getDistance(cellX, cellY, cellZ,res);
								
								if (cDist < cell.getValue()) {
									cell.setValue(cDist);
									// check the direction of the normal
									cell.normal =tri.n;
									
									
								}
							}
						}
					}
				}
			}
		}
	}
	public void findOutline(int cX, int cY, int cZ) {

		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					Cell cell = grid[x][y][z];
					cell.reached = false;
				}
			}
		}
		Vector<Cell> candidates = new Vector<Cell>();
		candidates.add(grid[cX][cY][cZ]);
		for (int i = 0; i < candidates.size(); i++) {
			Cell cell = candidates.get(i);
			cell.reached = true;
		}

		while (candidates.size() > 0) {
			Vector<Cell> nextCandidates = new Vector<Cell>();
			for (int i = 0; i < candidates.size(); i++) {
				Cell cell = candidates.get(i);

				int sX = max(cell.getXK() - 1, 0);
				int sY = max(cell.getYK() - 1, 0);
				int sZ = max(cell.getZK() - 1, 0);

				int eX = min(cell.getXK() + 2, nX);
				int eY = min(cell.getYK() + 2, nY);
				int eZ = min(cell.getZK() + 2, nZ);

				for (int x = sX; x < eX; x++) {
					for (int y = sY; y < eY; y++) {
						for (int z = sZ; z < eZ; z++) {
							Cell nb = grid[x][y][z];
							if (!nb.reached && nb.getValue() > iso) {
								nb.reached = true;
								nextCandidates.add(nb);
							}
						}
					}
				}
			}
			candidates = nextCandidates;
		}
		for (int x = 0; x < nX; x++) {
			for (int y = 0; y < nY; y++) {
				for (int z = 0; z < nZ; z++) {
					Cell cell = grid[x][y][z];
					if (!cell.reached && cell.getValue() > iso) {
						cell.setValue(0);// 0
					}
				}
			}
		}
	}
	public class Cell implements IsMCVertex {

		short x, y, z;
		float d = Float.MAX_VALUE;
		Vec3D normal;
		boolean reached;
		MCube cube;

		public Cell(int x, int y, int z) {
			this.x = (short) x;
			this.y = (short) y;
			this.z = (short) z;
		}

		public float getValue() {
			// TODO Auto-generated method stub
			return d;
		}

		public void setValue(double value) {
			// TODO Auto-generated method stub
			this.d = (float) value;
		}

		public MCube getCube() {
			// TODO Auto-generated method stub
			return cube;
		}

		@Override
		public float getX() {
			// TODO Auto-generated method stub
			// return nX%3;
			return x * dim + sX;
			// return x;
		}

		@Override
		public float getY() {
			// TODO Auto-generated method stub
			return y * dim + sY;
			// return y;
		}

		public float getZ() {
			// TODO Auto-generated method stub
			return z * dim + sZ;
			// return z;
		}

		public int getXK() {
			// TODO Auto-generated method stub
			// return nX%3;
			return x;
			// return x;
		}

		public int getYK() {
			// TODO Auto-generated method stub
			return y;
			// return y;
		}

		public int getZK() {
			// TODO Auto-generated method stub
			return z;
			// return z;
		}

		public void setCube(MCube cube) {
			// TODO Auto-generated method stub
			this.cube = cube;
		}

		@Override
		public void setPosition(float x, float y, float z) {
			// TODO Auto-generated method stub

		}

		@Override
		public void setZ(float z) {
			// TODO Auto-generated method stub
			// this.z=z;
		}

		@Override
		public void setPosition(float x, float y) {
			// TODO Auto-generated method stub

		}

		@Override
		public void setX(float x) {
			// TODO Auto-generated method stub

		}

		@Override
		public void setY(float Y) {
			// TODO Auto-generated method stub

		}

		@Override
		public int getId() {
			// TODO Auto-generated method stub
			return 0;
		}

		@Override
		public void setId(int i) {
			// TODO Auto-generated method stub

		}
	}
}
